﻿#include "BaseApp.h"
#include "WinFunc.h"
#include "Misc.h"
#include <sstream>

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

namespace Base {

	LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
		return BaseApp::s_BaseApp->HandleMsg(hwnd, msg, wParam, lParam);
	}

	BaseApp* BaseApp::s_BaseApp = nullptr;

	BaseApp::BaseApp() :
		m_hWnd(NULL)
	{
		D_ASSERT(s_BaseApp == nullptr, "s_BaseApp already create!");
		s_BaseApp = this;


		// init comp
		m_log = std::make_unique<Log>();
		m_timer = std::make_unique<Timer>();
		m_graphics = std::make_unique<Graphics>();
		m_keyboard = std::make_unique<Keyboard>();
		m_mouse = std::make_unique<Mouse>();

		m_windowClzName = "My_DX_11_Demo";
	}

	BaseApp::~BaseApp() {
		if (m_hWnd != nullptr)
		{
			DestroyWindow(m_hWnd);
			m_hWnd = nullptr;
		}
	}

	void BaseApp::Init(HINSTANCE hInstance, int width, int height, const char* title) {
		m_hInstance = hInstance;
		m_Width = width;
		m_Height = height;
		m_title = title;

		InitWindow();
	}

	void BaseApp::InitWindow() {
		// 注册窗口
		WNDCLASSEX wc = { 0 };
		wc.cbSize = sizeof(wc);
		wc.style = CS_OWNDC;
		wc.lpfnWndProc = WndProc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hInstance = m_hInstance;
		wc.hIcon = nullptr;
		wc.hCursor = nullptr;
		wc.hbrBackground = nullptr;
		wc.lpszMenuName = nullptr;
		wc.lpszClassName = m_windowClzName.c_str();
		wc.hIconSm = nullptr;
		RegisterClassExA(&wc);

		//----------------
		DWORD windowStyle = WS_OVERLAPPEDWINDOW;
		BOOL hasMenu = false;

		// 调整窗口大小
		RECT rect;
		rect.left = rect.top = 100;
		rect.right = rect.left + m_Width;
		rect.bottom = rect.top + m_Height;
		BOOL success = AdjustWindowRect(&rect, windowStyle, hasMenu);
		D_ASSERT(success, "AdjustWindowRect error!");

		m_hWnd = CreateWindowExA(
			0,
			m_windowClzName.c_str(),
			m_title.c_str(),
			windowStyle,
			rect.left,
			rect.top,
			rect.right - rect.left,
			rect.bottom - rect.top,
			nullptr,
			nullptr,
			m_hInstance,
			nullptr
		);
		D_ASSERT(m_hWnd != NULL, "CreateWindowExA error!");

		ShowWindow(m_hWnd, SW_SHOWDEFAULT);
		UpdateWindow(m_hWnd);

		// TODO: init graphics
		m_graphics->Init(m_hWnd, m_Width, m_Height);
		m_graphics->SetClearColor(0, 0, 0);

		// init imgui
		InitImGUI();
	}

	void BaseApp::InitImGUI() {
		// Setup Dear ImGui context
		IMGUI_CHECKVERSION();
		ImGui::CreateContext();
		ImGuiIO& io = ImGui::GetIO(); (void)io;
		//io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
		//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls

			// Setup Dear ImGui style
		ImGui::StyleColorsDark();
		//ImGui::StyleColorsLight();

		// Setup Platform/Renderer backends
		ImGui_ImplWin32_Init(m_hWnd);
		ImGui_ImplDX11_Init(m_graphics->GetDevice(), m_graphics->GetDeviceContext());
	}

	void BaseApp::ReleaseImGUI() {
		// Cleanup
		ImGui_ImplDX11_Shutdown();
		ImGui_ImplWin32_Shutdown();
		ImGui::DestroyContext();

		DestroyWindow(m_hWnd);
		UnregisterClass(m_windowClzName.c_str(), m_hInstance);
	}

	void  BaseApp::StartMainLoop() {

		SetIMGUI(true);
		OnInit();

		MSG msg = {};
		while (msg.message != WM_QUIT)
		{
			if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else
			{
				m_timer->Tick();
				BeginFrame();
				OnFrame();
				EndFrame();
				Sleep(1);
			}
		}

		OnTerminate();
		ReleaseImGUI();
	}

	void BaseApp::BeginFrame() {
		if (mEnableIMGUI) {
			ImGui_ImplDX11_NewFrame();
			ImGui_ImplWin32_NewFrame();
			ImGui::NewFrame();
			OnGUI();
		}
		OnInput();
		OnUpdate();
	}
	void BaseApp::EndFrame() {
		if (mEnableIMGUI)
		{
			ImGui::Render();
			ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
		}
		m_graphics->Present();
	}


	LRESULT BaseApp::HandleMsg(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

		if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wParam, lParam)) {
			return true;
		}

		switch (msg)
		{
		case WM_SYSCOMMAND:
			// Disable ALT application menu
			if ((wParam & 0xfff0) == SC_KEYMENU) {
				return 0;
			}
			break;
		case WM_SIZE:
		{
			//LOGINFO("wm_size called!");
			if (m_graphics != nullptr &&
				m_graphics->GetDevice() != nullptr &&
				wParam != SIZE_MINIMIZED)
			{
				// 
				auto newWidth = (UINT)LOWORD(lParam);
				auto newHeight = (UINT)HIWORD(lParam);
				m_Width = newWidth;
				m_Height = newHeight;
				m_graphics->Resize(m_Width, m_Height);
				OnResize();
			}
		}
		break;
		case WM_CLOSE:
			PostQuitMessage(0);
			break;

		case WM_KILLFOCUS:
			// 窗口失去焦点
			m_keyboard->ClearState();
			break;

		case WM_KEYDOWN:
		case WM_SYSKEYDOWN:
			// keydown
		{
			unsigned char code = static_cast<unsigned char>(wParam);
			m_keyboard->OnKeyPresed(code);
		}
		break;

		case WM_KEYUP:
		case WM_SYSKEYUP:
			// keyup
		{
			unsigned char code = static_cast<unsigned char>(wParam);
			m_keyboard->OnKeyReleased(code);
		}
		break;
		case WM_CHAR:
		{
			unsigned char code = static_cast<unsigned char>(wParam);
			m_keyboard->OnChar(code);
		}
		break;
		case WM_MOUSEMOVE:
			// 鼠标移动
		{
			const POINTS pt = MAKEPOINTS(lParam);
			// in client region -> log move, and log enter + capture mouse (if not previously in window)
			if (pt.x >= 0 && pt.x < m_Width && pt.y >= 0 && pt.y < m_Height)
			{
				m_mouse->OnMouseMove(pt.x, pt.y);
				if (!m_mouse->IsInWindow())
				{
					SetCapture(m_hWnd);
					m_mouse->OnMouseEnter();
				}
			}
			// not in client -> log move / maintain capture if button down
			else
			{
				ReleaseCapture();
				m_mouse->OnMouseLeave();

				//if (wParam & (MK_LBUTTON | MK_RBUTTON))
				//{
				//	m_mouse->OnMouseMove(pt.x, pt.y);
				//}
				//// button up -> release capture / log event for leaving
				//else
				//{
				//	ReleaseCapture();
				//	m_mouse->OnMouseLeave();
				//}
			}
		}
		break;
		case WM_LBUTTONDOWN:
			// left mouse button down
		{
			const POINTS pt = MAKEPOINTS(lParam);
			m_mouse->OnLeftPressed(pt.x, pt.y);
		}
		break;
		case WM_LBUTTONUP:
			// left mouse button up
		{
			const POINTS pt = MAKEPOINTS(lParam);
			m_mouse->OnLeftReleased(pt.x, pt.y);

			// release mouse if outside of window
			if (pt.x < 0 || pt.x >= m_Width || pt.y < 0 || pt.y >= m_Height)
			{
				ReleaseCapture();
				m_mouse->OnMouseLeave();
			}
		}
		break;

		case WM_RBUTTONDOWN:
			// right mouse button down
		{
			const POINTS pt = MAKEPOINTS(lParam);
			m_mouse->OnRightPressed(pt.x, pt.y);
		}
		break;
		case WM_RBUTTONUP:
			// right mouse button up
		{
			const POINTS pt = MAKEPOINTS(lParam);
			m_mouse->OnRightReleased(pt.x, pt.y);
			// release mouse if outside of window
			if (pt.x < 0 || pt.x >= m_Width || pt.y < 0 || pt.y >= m_Height)
			{
				ReleaseCapture();
				m_mouse->OnMouseLeave();
			}
		}
		break;
		case WM_MOUSEWHEEL:
			// 鼠标滚轮
			// TODO: 暂不实现.
			break;
		default:
			break;
		}
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
}